package com.spark.sniffer.plugin.plugins.interceptor;

import java.lang.reflect.Method;

import com.spark.sniffer.constant.PassThroughEnum;
import com.spark.sniffer.logging.api.Logger;
import com.spark.sniffer.logging.api.LoggerFactory;
import com.spark.sniffer.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import com.spark.sniffer.plugin.interceptor.enhance.MethodInterceptResult;
import com.spark.sniffer.tracing.Span;
import com.spark.sniffer.tracing.TraceId;
import com.spark.sniffer.tracing.TracingContext;
import com.spark.sniffer.util.ReflectUtil;
import com.spark.sniffer.util.ReflectUtil.WrapperMethod;

public class AliyunMessageListenerInterceptor implements InstanceMethodsAroundInterceptor {
    private static final Logger log = LoggerFactory.getLogger(AliyunMessageListenerInterceptor.class);

    @Override
    public void beforeMethod(Object instance, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        MethodInterceptResult result) throws Throwable {
        // com.aliyun.openservices.ons.api.Message
        Object message = allArguments[0];
        if (message == null) {
            return;
        }

        // 从消息体properties中获取透传数据设置到上下文中
        WrapperMethod getPropertyMethod = ReflectUtil.getMethod(message, "getUserProperties", String.class);
        for (PassThroughEnum passThrough : PassThroughEnum.values()) {
            String key = passThrough.getValue();
            String value = getPropertyMethod.invoke(key);

            // traceid特别处理
            if (TraceId.KEY.equals(key)) {
                if (value == null) {
                    value = TraceId.generate();
                }
                TraceId.putToContext(value);
            }

            if (value == null || value.trim().equals("")) {
                continue;
            }

            TracingContext.getContext().putPassThrough(key, value);
            log.debug("put passThrough to context: {}={}", key, value);
        }

        TracingContext.getContext().entrance(Span.Type.MQConsumer,
            method.getDeclaringClass().getSimpleName() + "." + method.getName());
    }

    @Override
    public Object afterMethod(Object instance, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        Object ret) throws Throwable {

        try {
            TracingContext.getContext().stopEntrance(Span.DEFAULT_CODE);
            printCallStack();
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            // DO remember this!!!
            TracingContext.removeContext();
        }

        return ret;
    }

    @Override
    public void handleMethodException(Object instance, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        Throwable t) {
        TracingContext.getContext().stopEntrance(Span.ERR_CODE);
    }

    private void printCallStack() {
        Span entranceSpan = TracingContext.getContext().getEntrance();
        if (entranceSpan == null) {
            return;
        }

        log.info("tid={}, consumer={}, code={}, ts={}, trace=[{}], pass=[{}]", TracingContext.getContext().getTraceId(),
            entranceSpan.getName(), entranceSpan.getCode(), entranceSpan.getDuration(),
            TracingContext.getContext().callstack2string(), TracingContext.getContext().passThrough2string());
    }

}
